/*
 * Copyright © OpenAtom Foundation.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package io.iec.edp.caf.databaseobject;

import com.alibaba.fastjson.JSON;
import io.iec.edp.caf.commons.core.SerializerFactory;
import io.iec.edp.caf.commons.core.api.DataSerializer;
import io.iec.edp.caf.commons.core.enums.SerializeType;
import io.iec.edp.caf.databaseobject.api.entity.DBInfo;
import io.iec.edp.caf.databaseobject.api.entity.DbType;
import io.iec.edp.caf.databaseobject.common.DatabaseObjectCommonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.Banner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.util.StringUtils;

import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author liu_wei
 */
@Slf4j
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class DboDeploy implements CommandLineRunner {
    private static String dboPath;
    private static DbType dbType;
    private static String server;
    private static String dbName;
    private static String userName;
    private static String passWord;
    private static String port;
    private static List<String> year;
    private static String url;
    private static DBInfo dbInfo;
    private static Map<String, List<String>> dimensionInfo;

    private static boolean isDbSetup;

    public static void main(final String[] args) {
        try
        {
            SpringApplication app = new SpringApplication(DboDeploy.class);
            if (args.length > 1) {
                //外部调用不展示banner
                app.setBannerMode(Banner.Mode.OFF);
                app.setLogStartupInfo(false);
                if (args.length == 7 && args[6].equals("0")) {
                    isDbSetup = true;
                } else {
                    isDbSetup = false;
                }
            }
            app.run(args);
        }
        catch (Error | Exception e)
        {
            log.error("部署数据库对象报错:" , e);
        }
    }

    @Override
    public void run(String... args) throws Exception {
        try{
            boolean invoked = false;
            DatabaseObjectCommonUtil.isToolEntrance = true;
            if (args.length > 0) {
                if (Arrays.asList(args).contains("patch-tool-invoke")) {
                    invoked = true;
                }
            }
            if (invoked) {
                while (true) {
                    DboDeployRequest request = getRequest();
                    if (request == null) {
                        System.exit(0);
                        return;
                    }
                    isDbSetup = request.isDbSetup();
                    getCmdParams(request.convertToArgs());
                    dbInfo = new DBInfo();
                    dbInfo.setDbName(dbName);
                    dbInfo.setDbType(dbType);
                    dbInfo.setPassWord(passWord);
                    dbInfo.setPort(port);
                    dbInfo.setServer(server);
                    dbInfo.setUserName(userName);
                    if (StringUtils.hasText(url)) {
                        dbInfo.setUrl(url);
                    }
                    dimensionInfo = new HashMap<>();
                    dimensionInfo.put("FIYear", year);
                    deployCycleV2();
                }
            } else {
                boolean isCommand = false;
                if (args.length > 1) {
                    getCmdParams(args);
                    isCommand = true;
                } else {
                    getDbInfo();
                }
                dbInfo = new DBInfo();
                dbInfo.setDbName(dbName);
                dbInfo.setDbType(dbType);
                dbInfo.setPassWord(passWord);
                dbInfo.setPort(port);
                dbInfo.setServer(server);
                dbInfo.setUserName(userName);
                if (StringUtils.hasText(url)) {
                    dbInfo.setUrl(url);
                }
                dimensionInfo = new HashMap<>();
                dimensionInfo.put("FIYear", year);
                deployCycle(isCommand);
            }
        }catch (Throwable e){
            log.error("dbo工具部署报错{}",e);
        }
    }



    private static void deployCycle(boolean isCommand) {
        String result = "";
        try {
            DboDeployManager deployManager = new DboDeployManager();
            if (isDbSetup) {
                deployManager.deployDboWithDimensionListForDbSetup(dboPath, dimensionInfo, dbInfo);
            } else {
                deployManager.deployDboWithDimensionList(dboPath, dimensionInfo, dbInfo);
            }
            result = "dbo部署完成";
        } catch (Exception e) {
            result = "dbo部署出错";
            String errMsg = "部署数据库对象报错:" + e.getMessage();
            log.error(errMsg, e);
        }

        if (!isCommand) {
            checkIsContinue(false, result);
        } else {
            System.out.println("dbo部署完成");
            System.exit(0);
        }
    }

    private static String deployCycleV2() {
        String result = "";
        try {
            DboDeployManager deployManager = new DboDeployManager();
            if (isDbSetup) {
                deployManager.deployDboWithDimensionListForDbSetup(dboPath, dimensionInfo, dbInfo);
            } else {
                deployManager.deployDboWithDimensionList(dboPath, dimensionInfo, dbInfo);
            }
            result = "dbo部署完成";
        } catch (Exception e) {
            result = "dbo部署出错";
            String errMsg = "部署数据库对象报错:" + e.getMessage();
            log.error(errMsg, e);
        }
        return result;
    }


    private static void checkIsContinue(boolean isCommand, String message) {
        Scanner scanner = new Scanner(System.in);
        System.out.println(message + "是否继续Y/N？【Y】(重新输入DBO路径进行部署，如果需要切换数据库连接，请关闭工具重新运行)");
        String isContinue = scanner.nextLine();
        if (isContinue == null || isContinue.length() <= 0 || isContinue.trim().toLowerCase().equals("y")) {
            getDBOPath(scanner);
            deployCycle(isCommand);
        } else if (!isContinue.trim().toLowerCase().equals("y") && !isContinue.trim().toLowerCase().equals("n")) {
            checkIsContinue(isCommand, message);
        } else {
            System.exit(0);
        }
    }

    private static void getDbInfo() {
        Scanner scanner = new Scanner(System.in);
        getDBOPath(scanner);
        System.out.println("请选择数据库类型：1.PgSql 2.SqlServer 3.Oracle 4.DM 5.Highgo 6.MySQL 7.Oscar 8.Kingbase 9.DB2 10.OpenGauss 11.GBase8s 12.GBase8c[1]");
        String type = scanner.nextLine();
        checkDbType(type, scanner);

        System.out.println("请输入数据库服务器:[localhost]");
        server = scanner.nextLine();
        checkServer();
        String defaultPort = getDefaultPort();

        System.out.println("请输入数据库端口号:[" + defaultPort + "]");
        port = scanner.nextLine();
        checkPort(defaultPort, scanner);

        System.out.println("请输入数据库名(服务名):");
        dbName = scanner.nextLine();
        checkDbName(scanner);

        //String defaultUserName = getDefaultUser();
        System.out.println("请输入用户名:");
        userName = scanner.nextLine();
        checkUserName(scanner);

        System.out.println("请输入密码(控制台不显示密码信息)");
        Console cons = System.console();
        passWord = new String(cons.readPassword());
        checkPassWord(cons);

        System.out.println("请输入年度:[" + getCurrentYear() + "](多个年度，使用\",\"隔开，如果已经部署过多个年度，需要都输入)");
        year = Arrays.asList(scanner.nextLine().split(","));
        checkYear();
    }

    private static void getCmdParams(String[] args) {
        log.debug("命令行调用参数：" + Arrays.toString(args));
        dboPath = args[0].replaceAll("\"", "");
        checkDbType(args[1], null);
        url = args[2];
        //MySQL需要DBName
        if (dbType == DbType.MySQL) {
            String mysqlUrl = url.split("[?]")[0];
            dbName = mysqlUrl.substring(mysqlUrl.lastIndexOf("/") + 1);
        }
        if (dbType == DbType.SQLServer) {
            dbName = url.split("database=")[1];
            if (dbName.contains(";")) {
                dbName = dbName.split(";")[0];
            }
        }
        userName = args[3];
        passWord = args[4];
        DataSerializer serializer = SerializerFactory.getSerializer(SerializeType.Json);
        year = serializer.deserialize(args[5], List.class, String.class);
    }

    private static String getDefaultPort() {
        String defaultPort = "";
        switch (dbType) {
            case PgSQL:
                defaultPort = "5432";
                break;
            case SQLServer:
                defaultPort = "1433";
                break;
            case Oracle:
                defaultPort = "1521";
                break;
            case DM:
                defaultPort = "5236";
                break;
            case HighGo:
                defaultPort = "5866";
                break;
            case MySQL:
                defaultPort = "3306";
                break;
            case Oscar:
                defaultPort = "2003";
                break;
            case Kingbase:
                defaultPort = "54321";
                break;
            case DB2:
                defaultPort = "50000";
                break;
            case OpenGauss:
                defaultPort = "5432";
                break;
            case GBase8s:
                defaultPort = "15432";
                break;
            case GBase8c:
                defaultPort = "15432";
                break;
            default:
                defaultPort = "5432";
                break;
        }
        return defaultPort;
    }

    private static String getDefaultUser() {
        String defaultUserName = "";
        switch (dbType) {
            case PgSQL:
                defaultUserName = "postgres";
                break;
            case SQLServer:
                defaultUserName = "sa";
                break;
            case Oracle:
                defaultUserName = "system";
                break;
            case DM:
                defaultUserName = "sysdba";
                break;
            case HighGo:
                defaultUserName = "highgo";
                break;
            case MySQL:
                defaultUserName = "root";
                break;
            case Oscar:
                defaultUserName = "sysdba";
                break;
            case Kingbase:
                defaultUserName = "system";
                break;
            case DB2:
                defaultUserName = "db2inst1";
                break;
            case OpenGauss:
                defaultUserName = "omm";
                break;
            case GBase8s:
                defaultUserName = "gbase";
                break;
            case GBase8c:
                defaultUserName = "gbase";
            default:
                defaultUserName = "postgres";
                break;
        }
        return defaultUserName;
    }

    private static void getDBOPath(Scanner scanner) {
        System.out.println("请输入DBO路径");
        dboPath = scanner.nextLine();
        checkDboPath(scanner);
    }

    private static void checkDboPath(Scanner scanner) {
        dboPath = dboPath.trim();
        File file = new File(dboPath);
        if (dboPath == null || dboPath.length() <= 0) {
            System.out.println("请输入DBO路径");
            dboPath = scanner.nextLine();
            checkDboPath(scanner);

        } else if (!file.exists()) {
            System.out.println("DBO路径不存在，请重新输入：");
            dboPath = scanner.nextLine();
            checkDboPath(scanner);
        }
        //if()
    }


    private static void checkServer() {
        if (server == null || server.length() <= 0) {
            server = "localhost";
        }
    }

    private static void checkDbType(String type, Scanner scanner) {
        if (type == null || type.length() <= 0) {
            dbType = DbType.PgSQL;
        } else if ("1".equals(type) || "PgSQL".equals(type)) {
            dbType = DbType.PgSQL;

        } else if ("2".equals(type) || "SQLServer".equals(type)) {
            dbType = DbType.SQLServer;

        } else if ("3".equals(type) || "Oracle".equals(type)) {
            dbType = DbType.Oracle;

        } else if ("4".equals(type) || "DM".equals(type)) {
            dbType = DbType.DM;

        } else if ("5".equals(type) || "HighGo".equals(type)) {
            dbType = DbType.HighGo;

        } else if ("6".equals(type) || "MySQL".equals(type)) {
            dbType = DbType.MySQL;

        } else if ("7".equals(type) || "Oscar".equals(type)) {
            dbType = DbType.Oscar;

        } else if ("8".equals(type) || "Kingbase".equals(type)) {
            dbType = DbType.Kingbase;

        } else if ("9".equals(type) || "DB2".equals(type)) {
            dbType = DbType.DB2;

        } else if ("10".equals(type) || "OpenGauss".equals(type)) {
            dbType = DbType.OpenGauss;

        }else if ("11".equals(type) || "GBase8s".equals(type)) {
            dbType = DbType.GBase8s;

        } else if ("12".equals(type) || "GBase8c".equals(type)) {
            dbType = DbType.GBase8c;

        } else {
            System.out.println("请选择数据库类型：1.PgSql 2.SqlServer 3.Oracle 4.DM 5.Highgo 6.MySQL 7.Oscar  8.Kingbase  9.DB2 10.OpenGauss 11.GBase8s 12.GBase8c[1]");
            type = scanner.nextLine();
            checkDbType(type, scanner);
        }
    }

    private static Pattern number = Pattern.compile("^-?\\d+(\\.\\d+)?$");

    private static void checkPort(String defaultPort, Scanner scanner) {
        if (port == null || port.length() <= 0) {
            port = defaultPort;
        } else {
            Matcher isNum = number.matcher(port);
            if (!isNum.matches()) {
                System.out.println("端口号不规范，请重新输入:[" + defaultPort + "]");
                port = scanner.nextLine();
                checkPort(defaultPort, scanner);
            }
        }
    }

    private static void checkDbName(Scanner scanner) {
        if (dbName == null || dbName.length() <= 0) {
            System.out.println("请输入数据库名(服务名):");
            dbName = scanner.nextLine();
            checkDbName(scanner);
        }
    }

    private static void checkUserName(Scanner scanner) {
        if (userName == null || userName.length() <= 0) {
            System.out.println("请输入数据库用户");
            userName = scanner.nextLine();
            checkUserName(scanner);
        }
    }

    private static void checkPassWord(Console scanner) {
        if (passWord == null || passWord.length() <= 0) {
            System.out.println("请输入密码");
            passWord = new String(scanner.readPassword());
            checkPassWord(scanner);
        }
    }

    private static void checkYear() {
        if (year.size() == 1 && year.get(0).equals("")) {
            year = new ArrayList<>();
            year.add(getCurrentYear());
        }
    }

    private static String getCurrentYear() {
        Calendar calendar = Calendar.getInstance();
        return String.valueOf(calendar.get(Calendar.YEAR));
    }

    private static String getCurrentLanguage() {
        return "_CHS";
    }

    private static final String SEND_PLEASE_INPUT_ARGS = "please input args";
    private static final String RECEVIE_END_INPUT_ARGS = "end input args";
    private static final String RECEVIE_EXIT_SIGNAL = "exit";

    private DboDeployRequest getRequest() throws IOException {
        System.out.println(SEND_PLEASE_INPUT_ARGS);
        String msg = null;
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader localReader = new BufferedReader(
                new InputStreamReader(System.in,"UTF-8"));
        while (true) {
            msg = localReader.readLine();
            if (msg.equals(RECEVIE_END_INPUT_ARGS)) {
                break;
            } else if (msg.equals(RECEVIE_EXIT_SIGNAL)) {
                return null;
            } else {
                stringBuilder.append(msg);
            }
        }
        return JSON.parseObject(stringBuilder.toString(), DboDeployRequest.class);
    }

}
